#include <iostream>
#include <chrono>
#include <fstream>
#include <string>
#include <vector>
#include <regex>
#include <mutex>
#include <sstream>
#include <algorithm>

#include <dirent.h>
#include <string.h>
#include <iconv.h> 

#include <pcrecpp.h>
#include <sys/stat.h>
#include <unistd.h>

std::string getTimeStr() {
    time_t timep;
    time (&timep);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S",localtime(&timep) );
    return tmp;
}

template< typename ... Args  >
static auto print( Args ... x ) {
    auto printer = []( auto y ) { std::cout << y; };

    static std::mutex mutex;
    std::lock_guard<std::mutex> lock(mutex);

    auto ans =  { 0, ( printer(x), 0 )..., 0 };
    std::cout << std::endl;
    return ans;
}

template< typename ... Args  >
static auto log( Args ... x ) {
    std::ofstream fout( "log.txt", std::ios::app );
    if (!fout) return;
    auto printer = [&fout]( auto y ) { fout << y; };

    auto time_str = getTimeStr();

    static std::mutex mutex;
    std::lock_guard<std::mutex> lock(mutex);

    fout << "[" << time_str << "] ";
    auto ans =  { 0, ( printer(x), 0 )..., 0 };
    fout << std::endl;
    fout.close();
}

class Tools {
public:
    //获取目录下的文件及子目录, dirpath不要有最后/
    static bool list_dir_file(
            const std::string& dirpath,
            std::vector<std::string> *files,
            std::vector<std::string> *dirs,
            bool recurse = true, // 递归
            bool excludedotdir = true) { //排除.开始的目录
        DIR* task = opendir(dirpath.c_str());
        if (!task || (!files && !dirs)) {
            perror(dirpath.c_str());
            return false;
        }

        struct dirent* dent;
        while ((dent = readdir(task))) {
            if (strcmp(dent->d_name, ".") == 0 ||
                strcmp(dent->d_name, "..") == 0)
                continue;
            if (excludedotdir && dent->d_type & DT_DIR && dent->d_name[0] == '.')
                continue;
            if (dent->d_type & DT_REG) {
                if (files) {
                    files->push_back(dirpath);
                    files->back() += '/';
                    files->back() += dent->d_name;
                }
            }
            if (dent->d_type & DT_DIR) {
                if (dirs) {
                    dirs->push_back(dirpath);
                    dirs->back() += '/';
                    dirs->back() += dent->d_name;
                }
                if (recurse) {
                    std::string path2(dirpath);
                    path2 += '/';
                    path2 += dent->d_name;
                    list_dir_file(path2, files, dirs, recurse, excludedotdir);
                }
            }
        }
        closedir(task);
        return true;
    }

    static bool is_file(const std::string &f, const std::string &ch) {
        if (ch.empty()) return true;
        int l = f.length();
        int lch = ch.length();
        for (int i=lch-1; i>=0; --i) {
            if (f[l-i-1] != ch[lch-i-1])
                return false;
        }
        return true;
    }

    static bool has_dir(const std::string &path) {
        return (access(path.c_str(), 0) == 0);
    }

    static bool has_file(const std::string &path) {
        return (access(path.c_str(), 0) == 0);
    }

    static std::shared_ptr<std::vector<std::string>> list_file(
            const std::string &dir,
            const std::string &ch,
            bool sub = true ) {
        auto files = std::make_shared<std::vector<std::string>>();
        std::vector<std::string> tmp;
        Tools::list_dir_file(dir, &tmp, nullptr);
        for (auto&& f : tmp) {
            if (is_file(f, ch))
               files->push_back(f);
        }
        return files;
    }

    static int run_cmd(const std::string &cmd) {
        return system(cmd.c_str());
    }

    static bool u7z(const std::string &file, const std::string &dst) {
        std::string cmd = "7z x -aoa -o";
        cmd += dst;
        cmd += " " + file;
        return run_cmd(cmd) == 0;
    }

    static bool copy(const std::string &src, const std::string &dst) {
        std::string cmd = "cp " + src + " " + dst;
        return run_cmd(cmd)==0;
    }

    static void empty_dir(const std::string &dir) {
        std::string cmd = "rm -rf " + path_join(dir, "*");
        run_cmd(cmd);
    }

    static void rm(const std::string &dir) {
        std::string cmd = "rm -rf " + dir;
        run_cmd(cmd);
    }

    static bool write_file(const std::string &file, const std::string &content, std::ios_base::openmode mode=std::ios::trunc) {
        std::ofstream fout( file, mode );
        if (!fout) return false;
        fout << content;
        fout.close();
        return true;
    }

    static void del_file(const std::string &file) {
        std::remove(file.c_str());
    }

    static std::string path_join(const std::string &root, const std::string &path) {
        std::string full = root;
        if (root[root.length()-1] != '/') {
            full = root + "/";
        }
        if (path[0] == '/') {
            full += path.substr(1);
        } else {
            full += path;
        }
        return std::move(full);
    }

    static std::string path_file_full_name(const std::string &path) {
        auto pos = path.rfind('/');
        if (pos == std::string::npos) {
            return path;
        }
        return std::move(path.substr(pos+1));
    }

    static std::string path_file_name(const std::string &path) {
        auto full = path_file_full_name(path);
        auto pos = full.rfind('.');
        if (pos == std::string::npos) {
            return full;
        }
        return std::move(full.substr(0, pos));
    }

    static std::string path_file_ext(const std::string &path) {
        auto pos = path.rfind('.');
        if (pos == std::string::npos) {
            return std::string();
        }
        return std::move(path.substr(pos+1));
    }

    static int gbk2utf8(const std::string &src, std::string &dst) {
        char *inbuf = (char*)src.c_str();
        size_t inlen = src.length();
        size_t srclen = inlen;
        dst.resize(srclen*3/2+4);
        char* outbuf = (char*)dst.c_str();
        char *srcbuf = inbuf;
        size_t outlen = srclen * 3 / 2 + 4;

        iconv_t cd;
        char **pin = &inbuf;
        char **pout = &outbuf;

        //utf8转换是没有BOM的
        cd = iconv_open("utf8//IGNORE", "gbk");
        if (cd == 0) {
            //perror("iconv_open");
            return -1;
        }

        bool failed = false;
        if (iconv(cd, pin, &inlen, pout, &outlen) == -1) {
            //perror("iconv");
            failed = true;
        }
        iconv_close(cd);
        if (failed) {
            return -1;
        }
        else {
            dst.resize(srclen * 3 / 2 + 4- outlen);
            return 0;
        }
    }

    static bool same_begin(const std::string & long_str, const std::string &short_str) {
        if (short_str.empty() || long_str.empty()) return false;
        int l = long_str.length();
        int lch = short_str.length();
        for (int i=0; i<lch; ++i) {
            if (long_str[i] != short_str[i])
                return false;
        }
        return true;
    }

    // get_html_text("<title>abcdef</title>", "title") => abcdef
    static std::string get_html_text(const std::string &html, const std::string &mark) {
        auto bp = html.find("<"+mark+">");
        if (bp == std::string::npos) return std::string();
        auto ep = html.rfind("</"+mark+">");
        if (ep == std::string::npos) return std::string();
        return html.substr(bp+mark.length()+2, ep-bp-mark.length()-2);
    }

    /*
        a、先将html文本中的所有空格、换行符去掉（因为html中的空格和换行是被忽略的）
        b、将<head>标记中的所有内容去掉
        c、将<script>标记中的所有内容去掉
        d、将<style>标记中的所有内容去掉
        e、将td换成空格，tr,li,br,p 等标记换成换行符
        f、去掉所有以“<>”符号为头尾的标记去掉。
        g、转换&amp;，&nbps;等转义字符换成相应的符号
        h、去掉多余的空格和空行
    */
    static std::string html2text_std(const std::string &html) {
        /*
        *   this has something worng.
        */
        //static std::mutex mutex;
        //std::lock_guard<std::mutex> lock(mutex);
    
        // remove line breaks,tabs
        static std::regex reg1("[\r\n\t]");
        std::string resp = std::regex_replace(html, reg1, "");

        // remove the header
        //static std::regex reg2("<head>.*?</head>");
        //resp = std::regex_replace(resp, reg2, "");

        // remove all scripts
        static std::regex reg3("<script[^>]*?>.*?</script>");
        resp = std::regex_replace(resp, reg3, "");

        // remove all styles
        //static std::regex reg4("<style[^>]*?>.*?</style>");
        static std::regex reg4("<style[\\w\\W]*?</style>");
        resp = std::regex_replace(resp, reg4, "");

        //
        static std::regex reg5("<( )*td([^>])*>");
        resp = std::regex_replace(resp, reg5, " ");

        static std::regex reg6("<( )*br( )*>");
        resp = std::regex_replace(resp, reg6, "\r");
        static std::regex reg7("<( )*li( )*>");
        resp = std::regex_replace(resp, reg7, "\r");
        static std::regex reg8("<( )*tr([^>])*>");
        resp = std::regex_replace(resp, reg8, "\r\r");
        static std::regex reg9("<( )*p([^>])*>");
        resp = std::regex_replace(resp, reg9, "\r\r");

        static std::regex reg10("<[^>]*>");
        resp = std::regex_replace(resp, reg10, " ");

        static std::regex reg11("&amp;");
        resp = std::regex_replace(resp, reg11, "&");
        static std::regex reg12("&nbsp");
        resp = std::regex_replace(resp, reg12, " ");
        static std::regex reg13("&lt;");
        resp = std::regex_replace(resp, reg13, "<");
        static std::regex reg14("&gt;");
        resp = std::regex_replace(resp, reg14, ">");
        static std::regex reg15("&(.{2,6});");
        resp = std::regex_replace(resp, reg15, " ");

        static std::regex reg16("( )+");
        resp = std::regex_replace(resp, reg16, " ");
        static std::regex reg17("(\r)( )+(\r)");
        resp = std::regex_replace(resp, reg17, "\r\r");
        static std::regex reg18("(\r\r)+");
        resp = std::regex_replace(resp, reg18, "\r\n");

        return std::move(resp);
    }

    static std::string html2text(const std::string &html) {
        /*
        *   maybe it's ok.
        */
        std::string resp = html;
        static auto re1 = pcrecpp::RE("[\r\n\t]");
        static auto re2 = pcrecpp::RE("<head>.*?</head>");
        static auto re3 = pcrecpp::RE("<script[^>]*?>.*?</script>");
        static auto re4 = pcrecpp::RE("<style[^>]*?>.*?</style>");

        static auto re5 = pcrecpp::RE("<td.*?>(.*?)</td>");
        static auto re6 = pcrecpp::RE("< *br *>");
        static auto re7 = pcrecpp::RE("< *li *>");
        static auto re8 = pcrecpp::RE("<tr.*?>(.*?)</tr>");
        static auto re9 = pcrecpp::RE("<p.*?>(.*?)</p>");

        static auto re10 = pcrecpp::RE("<[^>]*>");

        static auto re11 = pcrecpp::RE("&amp;");
        static auto re12 = pcrecpp::RE("&nbsp");
        static auto re13 = pcrecpp::RE("&lt;");
        static auto re14 = pcrecpp::RE("&gt;");
        static auto re15 = pcrecpp::RE("&(.{2,6});");
        static auto re16 = pcrecpp::RE(" {2,}");
        static auto re17 = pcrecpp::RE("\r +\r");
        static auto re18 = pcrecpp::RE("\r{4,}");
        static auto re19 = pcrecpp::RE("\r[\s|]*\n");

        re1.GlobalReplace("", &resp);
        re2.GlobalReplace("", &resp);
        re3.GlobalReplace("", &resp);
        re4.GlobalReplace("", &resp);
        
        re5.GlobalReplace(" ", &resp);
        re6.GlobalReplace("\r", &resp);
        re7.GlobalReplace("\r", &resp);
        re8.GlobalReplace("\r\r", &resp);
        re9.GlobalReplace("\r\r", &resp);
        
        re10.GlobalReplace(" ", &resp);

        re11.GlobalReplace("&", &resp);
        re12.GlobalReplace(" ", &resp);
        re13.GlobalReplace("<", &resp);
        re14.GlobalReplace(">", &resp);
        re15.GlobalReplace(" ", &resp);

        re16.GlobalReplace(" ", &resp);
        re17.GlobalReplace("\r\r", &resp);
        re18.GlobalReplace("\r\n", &resp);
        
        re18.GlobalReplace("", &resp);

        return std::move(resp);
    }

    static std::string gethtml(const std::string &text) {
        auto bpos = text.find("<html");
        auto epos = text.rfind("</html>");
        if (bpos == std::string::npos || epos == std::string::npos)
            return std::string();
        return text.substr(bpos, epos - bpos + 7);
    }

    static int64_t getTimeStamp() {
        std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds> tp = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());
        return tp.time_since_epoch().count();
    }

    template< typename T>
    static T str2any(const std::string& str) {
        T out;
        std::stringstream ss;
        ss << str;
        ss >> (out);
        return out;
    }

    template< typename T>
    static std::string any2str(const T &in) {
        std::string str;
        std::stringstream ss;
        ss << in;
        ss >> str;
        return std::move(str);
    }

    static bool is_str_gbk(const std::string& src) {
        const char* str = src.c_str();
        unsigned int nBytes = 0;//GBK可用1-2个字节编码,中文两个 ,英文一个 
        unsigned char chr = *str;
        bool bAllAscii = true; //如果全部都是ASCII,  

        for (unsigned int i = 0; str[i] != '\0'; ++i){
            chr = *(str + i);
            if ((chr & 0x80) != 0 && nBytes == 0){// 判断是否ASCII编码,如果不是,说明有可能是GBK
                bAllAscii = false;
            }

            if (nBytes == 0) {
                if (chr >= 0x80) {
                    if (chr >= 0x81 && chr <= 0xFE){
                        nBytes = +2;
                    }
                    else{
                        return false;
                    }

                    nBytes--;
                }
            }
            else{
                if (chr < 0x40 || chr>0xFE){
                    return false;
                }
                nBytes--;
            }//else end
        }

        if (nBytes != 0)  {		//违返规则 
            return false;
        }

        if (bAllAscii){ //如果全部都是ASCII, 也是GBK
            return true;
        }

        return true;
    }

    static bool is_str_utf8(const std::string & src) {
        const char* str = src.c_str();
        unsigned int nBytes = 0;//UFT8可用1-6个字节编码,ASCII用一个字节  
        unsigned char chr = *str;
        bool bAllAscii = true;

        for (unsigned int i = 0; str[i] != '\0'; ++i){
            chr = *(str + i);
            //判断是否ASCII编码,如果不是,说明有可能是UTF8,ASCII用7位编码,最高位标记为0,0xxxxxxx 
            if (nBytes == 0 && (chr & 0x80) != 0){
                bAllAscii = false;
            }

            if (nBytes == 0) {
                //如果不是ASCII码,应该是多字节符,计算字节数  
                if (chr >= 0x80) {

                    if (chr >= 0xFC && chr <= 0xFD){
                        nBytes = 6;
                    }
                    else if (chr >= 0xF8){
                        nBytes = 5;
                    }
                    else if (chr >= 0xF0){
                        nBytes = 4;
                    }
                    else if (chr >= 0xE0){
                        nBytes = 3;
                    }
                    else if (chr >= 0xC0){
                        nBytes = 2;
                    }
                    else{
                        return false;
                    }

                    nBytes--;
                }
            }
            else{
                //多字节符的非首字节,应为 10xxxxxx 
                if ((chr & 0xC0) != 0x80){
                    return false;
                }
                //减到为零为止
                nBytes--;
            }
        }

        //违返UTF8编码规则 
        if (nBytes != 0)  {
            return false;
        }

        if (bAllAscii){ //如果全部都是ASCII, 也是UTF8
            return true;
        }

        return true;
    }

    static auto file_size(const std::string &filename) {
        struct stat statbuf;
        ::stat(filename.c_str(), &statbuf);
        return statbuf.st_size;
    }

    static std::string trim(const std::string &str) {
        if (str.empty()) return str;
        auto s = str.find_first_not_of(" ");
        auto e = str.find_last_not_of(" ");
        return str.substr(s, e-s+1);
    }

    static std::shared_ptr<std::vector<std::string>> split(const std::string &src, const std::string &delim) {
        auto ptr = std::make_shared<std::vector<std::string>>();
        size_t last = 0;
        size_t index=src.find_first_of(delim,last);
        while (index!=std::string::npos) {
            ptr->push_back(src.substr(last,index-last));
            last=index+1;
            index=src.find_first_of(delim,last);
        }
        if (index-last>0) {
            ptr->push_back(src.substr(last,index-last));
        }
        return ptr;
    }

    static std::string get_charset(const std::string &html) {
        auto getStr = [](const std::string &str) -> std::string {
            auto bp = str.find("charset=");
            if (bp == std::string::npos) return std::string();
            auto ep = str.find("\"", bp+9);
            if (ep == std::string::npos) return std::string();

            auto resp = str.substr(bp+8, ep-bp-8);

            if (resp.at(0)=='\"') return resp.substr(1, resp.length()-1);

            return std::move(resp);
        };

        auto mpb = std::string::npos;
        auto mpe = std::string::npos;
        do {
            if (mpe == std::string::npos)
                mpb = html.find("<meta");
            else
                mpb = html.find("<meta", mpe);
            if (mpb == std::string::npos) break;
            mpe = html.find(">", mpb);
            if (mpe == std::string::npos) break;
            auto charset = getStr(html.substr(mpb, mpe-mpb+1));
            if (!charset.empty()) return charset;
        } while (mpb != std::string::npos);

        return std::string();
    }

    static std::string get_charset_regex(const std::string &html) {
        static auto re = pcrecpp::RE("<meta[^\n]*charset=[\"]*([^\n]*?)[\"| ]*/*>");
        std::string resp;
        re.PartialMatch(html, &resp);
        print(resp);
        return std::move(resp);
    }

    static std::string to_lower(const std::string &src) {
        std::string tmp = src;
        std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower);
        return std::move(tmp);
    }

    static std::string to_upper(const std::string &src) {
        std::string tmp = src;
        std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::toupper);
        return std::move(tmp);
    }
};
